module Payers
  class OrdersController < ControllerBase
    # GET /orders
    def index
      # cached "orders:#{current_payer.id}:index:#{params[:page]}:#{params[:per]}" do
        current_payer.orders.where(state: %w(new processing)).where('created_at < ?', 190.minutes.ago).each(&:reject)
        @orders = current_payer.order_histories
                      .joins(:payer, payment_request: [klass: [grade: [department: [:college]]]])
                      .includes(:payer, payment_request: [klass: [grade: [department: [:college]]]])
                      .order(id: :desc)
                      .page(params[:page])
                      .per(params[:per])
        render_page @orders, include: '**', each_serializer: OrderSerializer
      # end
    end

    def show
      # cached "orders:#{current_payer.id}:#{params[:id]}" do
        order = current_payer.orders.find(params[:id])
        order.reject if order and !order.completed? and order.created_at < 190.minutes.ago
        @order = current_payer.order_histories.find(params[:id])
        render json: @order, include: '**', serializer: OrderSerializer
      # end
    end

    # POST /orders
    def create
      college_id = AcademicPartitionHierarchy.where(descendant_id: params.require(:klass_id))
                       .where(distance: 3)
                       .pluck(:ancestor_id)
                       .first
      payment_request = PaymentRequest.paid(false)
                            .left_outer_joins(:order)
                            .left_outer_joins(plan: [targets: [:academic_partition]])
                            .includes(plan: [targets: [:academic_partition]])
                            .where('plans.active': true)
                            .where('targets.academic_partition_id': college_id)
                            .where(student_name: params.require(:student_name))
                            .first
      return head :not_found unless payment_request
      klass_id = payment_request.klass.id
      college_id = AcademicPartitionHierarchy.find_by(descendant_id: klass_id, distance: 3).ancestor_id
      return head :not_found unless payment_request.plan.targets.where(academic_partition_id: college_id).exists?
      Order.where(payment_request: payment_request).where(state: %w(new processing)).where('created_at < ?', 190.minutes.ago).each(&:reject)
      order = current_payer.orders.build(order_params.merge payment_request: payment_request)
      if order.save
        # expire_cache "orders:#{current_payer.id}:index:*" do
          render json: order, include: '**', serializer: OrderSerializer
        # end
      else
        render json: order.errors, status: :unprocessable_entity
      end
    end

    # PATCH /orders/:id
    def update
      payment_method = params.require(:payment_method)
      return render json: {payment_method: ['支付方式不合法']}, status: :unprocessable_entity unless payment_method.in? %w(wxpay alipay)
      order = current_payer.orders
                  .left_outer_joins(payment_request: [:plan])
                  .where(state: %w[new paying])
                  .where('plans.active': true)
                  .includes(payment_request: [:plan]).find(params[:id])
      result = send(:"#{payment_method}_response", order, params)
      if order.update(payment_method: payment_method, url_generated_at: Time.now, state: 'paying')
        # expire_cache "orders:#{current_payer.id}:index:*",
        #              "orders:#{current_payer.id}:#{params[:id]}" do
          render json: result
        # end
      else
        render json: order.errors, status: :unprocessable_entity
      end
    end

    # PATCH /orders/:id/paid
    def paid
      order = current_payer.orders.where(state: %w[paying succeeded]).find(params[:id])
      return head :no_content if order.succeeded?

      if order.update(state: 'processing')
        # expire_cache "orders:#{current_payer.id}:index:*",
        #              "orders:#{current_payer.id}:#{params[:id]}" do
          render json: order, serializer: OrderSerializer
        # end
      else
        render json: order.errors, status: :unprocessable_entity
      end
    end

    # PATCH /orders/:id/failed
    def failed
      order = current_payer.orders.where(state: 'paying').find(params[:id])
      return head :no_content if !order || order.completed?
      payment_state = fetch_payment_state(order)
      if payment_state == :not_found
        order.reject
        render json: order, serializer: OrderSerializer
      elsif !order.completed?
        order.update(state: 'processing') ?
            render(json: order, serializer: OrderSerializer) :
            render(json: order.errors, status: :unprocessable_entity)
      end
    end

    def destroy
      @order = current_payer.orders.where(state: %w[new paying]).find(params[:id])
      @order.reject('canceled')
      head :no_content
    end

    private

    def order_params
      params.permit(:credit_card_num, :credit_card_holder, :bank, :charge_id, :parent_name, :parent_mobile)
    end

    def alipay_response(order, params)
      redirect_url = if params[:redirect_url]
        params[:redirect_url]
            .gsub(/\B:id\b/, order.id.to_s)
      end
      url = ALIPAY_CLIENT.page_execute_url(
        method: 'alipay.trade.wap.pay',
        return_url: redirect_url,
        timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
        notify_url: alipay_payment_notifications_url,
        biz_content: {
            out_trade_no: order.order_num,
            product_code: 'QUICK_WAP_WAY',
            total_amount: order.charge.fee,
            subject: order.payment_request.plan.name,
            time_expire: 190.minutes.from_now.in_time_zone('Asia/Shanghai').strftime('%Y-%m-%d %H:%M')
        }.to_json
      )
      {url: url}
    end

    def wxpay_response(order, params)
      if from_wx_browser?
        generate_wx_js_params(order, params)
      else
        generate_wx_h5_url(order, params)
      end
    end

    def generate_wx_js_params(order, params)
      nonce = SecureRandom.uuid.tr!('-', '')
      prepay_id = WxPay::Service.invoke_unifiedorder(
        nonce_str: nonce,
        body: order.payment_request.plan.name,
        out_trade_no: order.order_num,
        total_fee: (order.charge.fee * 100).round,
        spbill_create_ip: request.remote_ip,
        trade_type: 'JSAPI',
        openid: params.require(:openid),
        notify_url: wxpay_payment_notifications_url
      ).fetch('prepay_id')

      WxPay::Service.generate_js_pay_req(
        prepayid: prepay_id,
        noncestr: nonce
      )
    end

    def generate_wx_h5_url(order, params)
      url = WxPay::Service.invoke_unifiedorder(
        body: order.payment_request.plan.name,
        out_trade_no: order.order_num,
        total_fee: (order.charge.fee * 100).round,
        spbill_create_ip: request.remote_ip,
        trade_type: 'MWEB',
        openid: params[:openid],
        notify_url: wxpay_payment_notifications_url
      ).fetch('mweb_url')
      if params[:redirect_url]
        redirect_url = params[:redirect_url]
            .gsub(/\B:id\b/, order.id.to_s)
        url << '&' << URI.encode_www_form(redirect_url: redirect_url)
      end
      {url: url}
    end

    def from_wx_browser?
      @from_wx_browser ||= request.headers['User-Agent'] =~ /\bMicroMessenger\b/i
    end

    # def generate_qr_code(url)
    #   RQRCode::QRCode.new(url).as_png(size: 300).to_s
    # end

    def fetch_payment_state(order)
      send("fetch_#{order.payment_method}_state", order)
    end

    def fetch_alipay_state(order)
      resp = ALIPAY_CLIENT.execute(
          method: 'alipay.trade.query',
          timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
          biz_content: {
              out_trade_no: order.order_num
          }.to_json
      )
      subcode = JSON.parse(resp)['alipay_trade_query_response']['sub_code']
      subcode == 'ACQ.TRADE_NOT_EXIST' ? :not_found : subcode
    end

    def fetch_wxpay_state(order)
      state = WxPay::Service.order_query({
        out_trade_no: order.order_num
      })[:raw]['xml']['err_code']
      state == 'ORDERNOTEXIST' ? :not_found : state
    end
  end
end
